package com.imooc.user.controller;

import com.imooc.auth.service.AuthService;
import com.imooc.auth.service.pojo.Account;
import com.imooc.auth.service.pojo.AuthCode;
import com.imooc.auth.service.pojo.AuthResponse;
import com.imooc.controller.BaseController;
import com.imooc.pojo.IMOOCJSONResult;
import com.imooc.pojo.ShopcartBo;
import com.imooc.user.UserApplicationProperties;
import com.imooc.user.pojo.Users;
import com.imooc.user.pojo.bo.UserBo;
import com.imooc.user.pojo.vo.UsersVO;
import com.imooc.user.service.UserService;
import com.imooc.user.stream.ForceLogoutTopic;
import com.imooc.utils.CookieUtils;
import com.imooc.utils.JsonUtils;
import com.imooc.utils.MD5Utils;
import com.imooc.utils.RedisOperator;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.*;

/**
 * @Author: 顾志杰
 * @Date: 2019/11/13
 * @Time: 15:14
 */
@Api(value = "注册登录", tags = {"注册接口"})
@RestController
@RequestMapping("passport")
@Slf4j
public class PassportController extends BaseController {
    @Autowired
    private UserService userService;
    @Autowired
    private RedisOperator redisOperator;
    @Autowired
    private UserApplicationProperties userApplicationProperties;
    @Autowired
    private AuthService authService;
    @Autowired
    private ForceLogoutTopic producer;

    private static final String AUTH_HEADER = "Authorization";
    private static final String REFRESH_TOKEN_HEADER = "refresh-token";
    private static final String UID_HEADER = "imooc-user-id";


    @ApiOperation(value = "判断用户名是否重复", notes = "判断用户名是否重复", httpMethod = "GET")
    @GetMapping("/usernameIsExist")
    public IMOOCJSONResult usernameIsExist(@RequestParam String username) {
        //1.判断用户名不能为空
        if (StringUtils.isBlank(username)) {
            return IMOOCJSONResult.errorMsg("用户名不能为空");
        }
        //2.查找注册的用户是否纯在
        boolean isExist = userService.queryUserNameIsExist(username);
        if (!isExist) {
            return IMOOCJSONResult.errorMsg("用户名已经存在");
        }
        //3.请求成功 用户名可以注册
        return IMOOCJSONResult.ok();
    }

    @ApiOperation(value = "注册", notes = "注册", httpMethod = "POST")
    @PostMapping("/regist")
    public IMOOCJSONResult regist(@RequestBody UserBo userBo,
                                  HttpServletRequest request,
                                  HttpServletResponse response) {
        // 是否开启注册功能
        if (userApplicationProperties.isDisableRegistration()) {
            log.info("user resgistration request is blocked - {}", userBo.getUsername());
            return IMOOCJSONResult.errorMsg("注册功能维护请稍后在试");
        }
        String username = userBo.getUsername();
        String password = userBo.getPassword();
        String confirmPassword = userBo.getConfirmPassword();
        //1.判断用户名密码不为空
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password) || StringUtils.isBlank(confirmPassword)) {
            return IMOOCJSONResult.errorMsg("用户名或密码不能为空");
        }
        //2.查询用户是否存在
        boolean isExist = userService.queryUserNameIsExist(username);
        if (!isExist) {
            return IMOOCJSONResult.errorMsg("用户名已经存在");
        }
        //3.密码长度不能少于6位
        if (password.length() < 6) {
            return IMOOCJSONResult.errorMsg("密码不能小于6位");
        }
        // 4.判断两次密码是否一致
        if (!password.equals(confirmPassword)) {
            return IMOOCJSONResult.errorMsg("两次密码输入不一致");
        }
        //5.实现注册
        Users users = userService.createUser(userBo);
        AuthResponse token = authService.tokenize(users.getId());
        if (AuthCode.SUCCESS.getCode().equals(token.getCode())) {
            log.error("Token error - uid ={}", users.getId());
            return IMOOCJSONResult.errorMsg("Token Error");
        }
        setNullProperty(users);

        //实现用户的redis会话
        UsersVO usersVO = conventUsersVO(users,token.getAccount().getToken());

        CookieUtils.setCookie(request, response, "user",
                JsonUtils.objectToJson(usersVO), true);

        //同步购物车数据
        this.synchShopcartData(usersVO.getId(), request, response);
        return IMOOCJSONResult.ok();
    }


    @ApiOperation(value = "登录", notes = "登录", httpMethod = "POST")
    @PostMapping("/login")
    @HystrixCommand(
            commandKey = "loginFail", // 全局唯一的标识服务，默认函数名称
            groupKey = "password", // 全局服务分组，用于组织仪表盘，统计信息。默认：类名
            fallbackMethod = "loginFail", //同一个类里，public private都可以
            // 在列表中的exception，不会触发降级
//            ignoreExceptions = {IllegalArgumentException.class}
            // 线程有关的属性
            // 线程组, 多个服务可以共用一个线程组
            threadPoolKey = "threadPoolA",
            threadPoolProperties = {
                    // 核心线程数
                    @HystrixProperty(name = "coreSize", value = "10"),
                    // coreSize > 0, LinkedBlockingQueue -> 请求等待队列
                    // 默认-1 , SynchronousQueue -> 不存储元素的阻塞队列（建议读源码，学CAS应用）
                    @HystrixProperty(name = "maxQueueSize", value = "40"),
                    // 在maxQueueSize=-1的时候无效，队列没有达到maxQueueSize依然拒绝
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
                    // （线程池）统计窗口持续时间
                    @HystrixProperty(name = "metrics.rollingStats.timeInMilliseconds", value = "2024"),
                    // （线程池）窗口内桶子的数量
                    @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "18"),
            }
//            ,
//            commandProperties = {
//                  // TODO 熔断降级相关属性，也可以放在这里
//            }
    )
    public IMOOCJSONResult login(@RequestBody UserBo userBo,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws Exception {
        String username = userBo.getUsername();
        String password = userBo.getPassword();
        //1.判断用户名密码不为空
        if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            return IMOOCJSONResult.errorMsg("用户名或密码不能为空");
        }
        // 2.实现登录
        Users users = userService.queryUserForLogin(username, MD5Utils.getMD5Str(password));
        if (!Optional.ofNullable(users).isPresent()) {
            return IMOOCJSONResult.errorMsg("用户名密码不正确");
        }
        AuthResponse token = authService.tokenize(users.getId());
        if (AuthCode.SUCCESS.getCode().equals(token.getCode())) {
            log.error("Token error - uid ={}", users.getId());
            return IMOOCJSONResult.errorMsg("Token Error");
        }
        //将token添加到Header中
        addAuth2Header(response,token.getAccount());

        setNullProperty(users);
        //实现用户的redis会话
        UsersVO usersVO = conventUsersVO(users,token.getAccount().getToken());
        CookieUtils.setCookie(request, response, "user",
                JsonUtils.objectToJson(usersVO), true);
        // 同步购物车数据
        this.synchShopcartData(usersVO.getId(), request, response);
        return IMOOCJSONResult.ok(users);
    }


    private void setNullProperty(Users users) {
        users.setPassword(null);
        users.setMobile(null);
        users.setEmail(null);
        users.setCreatedTime(null);
        users.setUpdatedTime(null);
        users.setBirthday(null);
    }

    @ApiOperation(value = "退出登录", notes = "退出登录", httpMethod = "POST")
    @PostMapping("/logout")
    public IMOOCJSONResult logout(@RequestParam String userId,
                                  HttpServletRequest request,
                                  HttpServletResponse response) {
        Account account = Account.builder()
                .token(request.getHeader(AUTH_HEADER))
                .refreshToken(request.getHeader(REFRESH_TOKEN_HEADER))
                .userId(userId)
                .build();
        AuthResponse auth = authService.delete(account);
        if (!AuthCode.SUCCESS.getCode().equals(auth.getCode())) {
            log.error("Token error - uid={}", userId);
            return IMOOCJSONResult.errorMsg("Token error");
        }

        //清除用户相关的cookie
        CookieUtils.deleteCookie(request, response, "user");
        // 用户退出登录，清除redis中user的信息
        redisOperator.del(REDIS_USER_TOKEN + ":" + userId);
        // 分布式会话中需要清除用户数据
        CookieUtils.deleteCookie(request, response, FOODIE_SHOPCART);
        return IMOOCJSONResult.ok();
    }

    /**
     * FIXME 将这个接口从网关层移除，不对外暴露
     * 简陋版api - 长得丑但是跑得快
     * @param userIds
     * @return
     */
    @ApiOperation(value = "用户强制退出登录", notes = "用户退出登录", httpMethod = "POST")
    @PostMapping("/forceLogout")
    public IMOOCJSONResult forceLogout(@RequestParam String userIds) {
        if (StringUtils.isNotBlank(userIds)) {
            for (String uid : userIds.split(",")) {
                log.info("send logout message, uid={}", uid);
                producer.output()
                        .send(MessageBuilder.withPayload(uid).build());
            }
        }
        return IMOOCJSONResult.ok();
    }


    /**
     * 注册登录成功后同步cookie和redis中的购物车数据
     */
    private void synchShopcartData(String userId,
                                   HttpServletRequest request,
                                   HttpServletResponse response) {

        /**
         * 1.redis中和cookie中为空，那么不做任何处理
         *   如果cookie中有数据redis里没有则同步数据
         * 2.redis中有数据，cookie中的购物车为空那么直接把redis中的数据同步cookie中
         * 如果cookie中的某个商品在redis中存在则删除redis中的数据以cookie为主
         * 3.同步到redis中去了以后，覆盖本地cookie购物车中的数据，保证本地购物车的数据是同步最新的
         */

        //从redis获取购物车
        String shopcartRedis = redisOperator.get(FOODIE_SHOPCART + ":" + userId);

        //从cookie获取购物车
        String shopcartCookie = CookieUtils.getCookieValue(request, FOODIE_SHOPCART, true);

        if (StringUtils.isBlank(shopcartRedis)) {
            //如果cookie中有数据redis里没有则同步数据
            if (StringUtils.isNotBlank(shopcartCookie)) {
                redisOperator.set(FOODIE_SHOPCART + ":" + userId, shopcartCookie);
            }
        } else {
            //redis不为空 cookie不为空合并cookie和redis中的购物车的商品数据（同一商品则覆盖redis）
            if (StringUtils.isNotBlank(shopcartCookie)) {

                /**
                 * 1. 已经存在的，把cookie中对应的数量，覆盖redis（参考京东）
                 * 2. 该项商品标记为待删除，统一放入一个待删除的list
                 * 3. 从cookie中清理所有的待删除list
                 * 4. 合并redis和cookie中的数据
                 * 5. 更新到redis和cookie中
                 */

                List<ShopcartBo> shopcartListRedis = JsonUtils.jsonToList(shopcartRedis, ShopcartBo.class);
                List<ShopcartBo> shopcartListCookie = JsonUtils.jsonToList(shopcartCookie, ShopcartBo.class);

                // 定义一个待删除list
                List<ShopcartBo> pendingDeleteList = new ArrayList<>();

                Objects.requireNonNull(shopcartListRedis).forEach(redis -> {
                    String redisSpecId = redis.getSpecId();
                    Objects.requireNonNull(shopcartListCookie).stream().filter(cookie -> redisSpecId.equals(cookie.getSpecId())).forEach(shopcart -> {
                        // 覆盖购买数量，不累加，参考京东
                        redis.setBuyCounts(shopcart.getBuyCounts());
                        // 把cookieShopcart放入待删除列表，用于最后的删除与合并
                        pendingDeleteList.add(shopcart);
                    });
                });


                if (shopcartListCookie != null) {
                    // 从现有cookie中删除对应的覆盖过的商品数据
                    shopcartListCookie.removeAll(pendingDeleteList);
                    // 合并两个list
                    shopcartListRedis.addAll(shopcartListCookie);
                    // 更新到redis和cookie
                    CookieUtils.setCookie(request, response, FOODIE_SHOPCART, JsonUtils.objectToJson(shopcartListRedis), true);
                    redisOperator.set(FOODIE_SHOPCART + ":" + userId, JsonUtils.objectToJson(shopcartListRedis));
                }

            } else {
                //redis不为空 cookie为空 覆盖cookie
                CookieUtils.setCookie(request, response, FOODIE_SHOPCART, shopcartRedis, true);
            }
        }
    }

    private UsersVO conventUsersVO(Users users,String token) {
        String uniqueToken = UUID.randomUUID().toString().trim();
        redisOperator.set(REDIS_USER_TOKEN + ":" + users.getId(), uniqueToken);

        UsersVO usersVO = new UsersVO();
        BeanUtils.copyProperties(users, usersVO);
        usersVO.setUserUniqueToken(uniqueToken);
        return usersVO;
    }

    private IMOOCJSONResult loginFail(UserBo userBO,
                                      HttpServletRequest request,
                                      HttpServletResponse response,
                                      Throwable throwable) throws Exception {
        return IMOOCJSONResult.errorMsg("验证码输错了（模仿12306）" + throwable.getLocalizedMessage());
    }

    private void addAuth2Header(HttpServletResponse response, Account token) {
        response.setHeader(AUTH_HEADER,token.getToken());
        // 让前端感知到，过期时间一天，这样可以在临近过期的时候refresh token
        Calendar expTime = Calendar.getInstance();
        expTime.add(Calendar.DAY_OF_MONTH, 1);
        response.setHeader("token-exp-time", expTime.getTimeInMillis() + "");

    }
}
